﻿using System;
using SoftwareConsulting.BI11.Studio.InternalServices;
using SoftwareConsulting.BI11.NumericalMethods.Statistics;
using SoftwareConsulting.BI11.NumericalMethods.Curves;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{
    /// <summary>
    /// Класс распределения Коши
    /// </summary>
    public sealed class CauchyDistribution : ProbabilityDensityFunction
    {	    
        private double _muDistributionMiddle;//центр распределения        
        private double _betaDistributionScale;//масштаб распределения

        /// <summary>
        /// Конструктор класса по умолчанию
        /// </summary>
        public CauchyDistribution() :
            this(0, 1)
        {
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="middle"></param>
        /// <param name="width"></param>
        public CauchyDistribution(double middle, double width)
        {
            _muDistributionMiddle = middle;
            _betaDistributionScale = width;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="histogram">Гистограмма, из которой вытягиваются параметры по наилучшим предположениям</param>
        public CauchyDistribution(Histogram histogram)
            : this (histogram.Average, 4 * histogram.Variance / 
            Math.Sqrt(Math.PI *( histogram.Minimum * histogram.Minimum +histogram.Maximum * histogram.Maximum)))
        {
        }

        /// <summary>
        /// Возвращает среднее распределения
        /// </summary>
        public override double Average
        {
            get 
            { 
                return _muDistributionMiddle; 
            }
            set
            {
                Assert.Fail(Assert.Id.Assert00277);//для распределения Коши недоступно
            }
        }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность, нахождения случайной переменной меньше или равной, чем X</returns>
        public override double GetDistributionValue(double x)
        {
            return Math.Atan((x - _muDistributionMiddle) / _betaDistributionScale) / Math.PI + 0.5;
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public override string Name
        {
            get 
            { 
                return "Cauchy distribution"; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public override double[] Parameters
        {
            get 
            { 
                return new double[] { _muDistributionMiddle, _betaDistributionScale }; 
            }
            set
            {
                this.MuDistributionMiddle = value[0];
                this.BetaDistributionScale = value[1];
            }
        }
        
        /// <summary>
        /// Возвращает значение, для которого функция распределения равна указанному значению X
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        protected override double GetInversedValue(double x)
        {
            return Math.Tan((x - 0.5) * Math.PI) * _betaDistributionScale + _muDistributionMiddle;
        }

        /// <summary>
        /// Возвращает/устанавливает масштаб распределения
        /// </summary>
        public double BetaDistributionScale
        {
            get
            {
                return _betaDistributionScale;
            }
            set 
            { 
                _betaDistributionScale = value; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает центр распределения
        /// </summary>
        public double MuDistributionMiddle
        {
            get
            {
                return _muDistributionMiddle;
            }
            set 
            { 
                _muDistributionMiddle = value; 
            }
        }

        /// <summary>
        /// Возвращает стандартное отклонение распределения
        /// </summary>
        public override double StandardDeviation
        {
            get 
            { 
                return double.NaN; 
            }
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format(Name + " ({0:####0.00000},{1:####0.00000})", _muDistributionMiddle, _betaDistributionScale);
        }

        /// <summary>
        /// Вычисляет значение, основываясь на функции плотности
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public override double GetValue(double x)
        {
            double dev = x - _muDistributionMiddle;
            return _betaDistributionScale / (Math.PI * (_betaDistributionScale * _betaDistributionScale + dev * dev));
        }
        
        /// <summary>
        /// Возвращает значение распределения и градиент распределения с учетом параметров
        /// </summary>
        /// <param name="x"></param>
        /// <returns>0й элемент - значение распределения, 1й и последующие - градиент распределения</returns>
        public override double[] GetValueAndGradient(double x)
        {            
            double[] result = new double[3];
            result[0] = this.GetValue(x);
            double y = x - _muDistributionMiddle;
            double r = 1 / (y * y + _betaDistributionScale * _betaDistributionScale);
            result[1] = 2 * result[0] * y * r;
            result[2] = result[0] * (1 / _betaDistributionScale - 2 * _betaDistributionScale * r);
            return result;
        }
    }
}
